import 'dart:convert';
import 'dart:io';

/// Generates two files from Flutter's `cupertino/icons.dart`:
/// 1) TypeScript declaration file (lib/src/cupertino_icons.d.ts)
///    exporting a string-valued enum `CupertinoIcons` with an entry for each
///    static IconData defined.
/// 2) Dart map file (lib/src/cupertino_icons_map_generated.dart)
///    mapping string icon names to `CupertinoIcons.<name>` values for runtime lookup.
///
/// Usage:
///   dart run tool/generate_cupertino_icons_d_ts.dart
///     - Discovers Flutter SDK via `flutter` in PATH and reads
///       packages/flutter/lib/src/cupertino/icons.dart
///     - Writes lib/src/cupertino_icons.d.ts
///
///   dart run tool/generate_cupertino_icons_d_ts.dart <path/to/icons.dart> [output.d.ts]
Future<void> main(List<String> args) async {
  // Resolve input path: prefer CLI arg; otherwise discover via flutter in PATH.
  String inputPath;
  if (args.isNotEmpty) {
    inputPath = args[0];
  } else {
    final sdkRoot = await _findFlutterSdkRoot();
    if (sdkRoot == null) {
      stderr.writeln('Unable to locate Flutter SDK via PATH or FLUTTER_ROOT.');
      stderr.writeln('Pass the path to icons.dart explicitly as the first argument.');
      exitCode = 2;
      return;
    }
    inputPath =
        File('$sdkRoot/packages/flutter/lib/src/cupertino/icons.dart').path;
  }

  // Default output paths inside this package.
  final outputDtsPath = args.length > 1 ? args[1] : 'lib/src/cupertino_icons.d.ts';
  final outputDartMapPath = 'lib/src/cupertino_icons_map_generated.dart';

  final file = File(inputPath);
  if (!await file.exists()) {
    stderr.writeln('Input file not found: ' + inputPath);
    exitCode = 2;
    return;
  }

  final content = await file.readAsString();

  // Extract all static const IconData names defined in class CupertinoIcons.
  // We match any line like: `static const IconData name = ...;`
  final iconNameRe = RegExp(
    r'static\s+const\s+IconData\s+(\w+)\s*=\s*',
    multiLine: true,
  );

  final Set<String> names = <String>{};
  for (final m in iconNameRe.allMatches(content)) {
    final name = m.group(1);
    if (name == null) continue;
    // Exclude private names if any slipped in (shouldn't, but be safe).
    if (name.startsWith('_')) continue;
    names.add(name);
  }

  if (names.isEmpty) {
    stderr.writeln('No IconData names found in: ' + inputPath);
    exitCode = 3;
    return;
  }

  final sorted = names.toList()
    ..sort((a, b) => a.toLowerCase().compareTo(b.toLowerCase()));

  // 1) Write .d.ts keys
  final StringBuffer outDts = StringBuffer();
  outDts.writeln('// Generated by tool/generate_cupertino_icons_d_ts.dart');
  outDts.writeln('// Do not edit by hand.');
  outDts.writeln();
  // Follow colors generator style: string-valued enum entries
  outDts.writeln('declare enum CupertinoIcons {');
  for (final name in sorted) {
    outDts.writeln('  ' + name + ' = ' + jsonEncode(name) + ',');
  }
  outDts.writeln('}');
  outDts.writeln();
  await File(outputDtsPath).writeAsString(outDts.toString());

  // 2) Write Dart map for lookup in runtime
  final StringBuffer outDart = StringBuffer();
  outDart.writeln('// AUTO GENERATED FILE, DO NOT EDIT.');
  outDart.writeln('//');
  outDart.writeln('// Generated by tool/generate_cupertino_icons_d_ts.dart');
  outDart.writeln("import 'package:flutter/cupertino.dart';");
  outDart.writeln();
  outDart.writeln('const Map<String, IconData> kCupertinoIconMap = {');
  for (final name in sorted) {
    outDart.writeln("  '" + name + "': CupertinoIcons." + name + ',');
  }
  outDart.writeln('};');
  outDart.writeln();
  await File(outputDartMapPath).writeAsString(outDart.toString());

  stdout.writeln('Wrote ' + outputDtsPath + ' and ' + outputDartMapPath + ' with ' + sorted.length.toString() + ' icons.');
}

Future<String?> _findFlutterSdkRoot() async {
  // 1) Check FLUTTER_ROOT environment variable.
  final env = Platform.environment['FLUTTER_ROOT'];
  if (env != null && env.isNotEmpty) {
    final candidate = Directory(env);
    if (await _looksLikeFlutterRoot(candidate)) return candidate.path;
  }

  // 2) Try `which flutter` to locate the binary and ascend to sdk root.
  try {
    final which = await Process.run('which', ['flutter']);
    if (which.exitCode == 0) {
      final p = (which.stdout as String).trim();
      if (p.isNotEmpty) {
        try {
          final resolved = File(p).resolveSymbolicLinksSync();
          final binDir = Directory(resolved).parent;
          final root = binDir.parent; // .../flutter/bin/flutter -> root is parent of bin
          if (await _looksLikeFlutterRoot(root)) return root.path;
        } catch (_) {
          // Fallthrough
        }
      }
    }
  } catch (_) {
    // ignore
  }

  // 3) Try `flutter --version --machine` JSON for flutterRoot (best effort).
  try {
    final proc = await Process.run('flutter', ['--version', '--machine']);
    if (proc.exitCode == 0) {
      final stdoutStr = (proc.stdout as String).trim();
      if (stdoutStr.isNotEmpty) {
        final dynamic jsonObj = json.decode(stdoutStr);
        final root = jsonObj is Map ? jsonObj['flutterRoot'] as String? : null;
        if (root != null) {
          final dir = Directory(root);
          if (await _looksLikeFlutterRoot(dir)) return dir.path;
        }
      }
    }
  } catch (_) {
    // ignore
  }

  return null;
}

Future<bool> _looksLikeFlutterRoot(Directory dir) async {
  try {
    final file = File(
      dir.path + '/packages/flutter/lib/src/cupertino/icons.dart',
    );
    return await file.exists();
  } catch (_) {
    return false;
  }
}
